package com.atguigu.grainmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.grainmall.product.service.CategoryBrandRelationService;
import com.atguigu.grainmall.product.vo.Catelog2Vo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.grainmall.product.dao.CategoryDao;
import com.atguigu.grainmall.product.entity.CategoryEntity;
import com.atguigu.grainmall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

/**
 * 商品三级分类
 *
 * @author shiyuan
 * @email aq30710@163.com
 * @date 2022-12-31 17:11:23
 */
@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 用LambdaQueryWrapper 的方式查询 比 baseMapper 形式 更加通用 兼容
     *     LambdaQueryWrapper<Equipment> queryWrapper1 = Wrappers.lambdaQuery();
     *     queryWrapper1.eq(Equipment::getEquipmentNo,serialNo);
     */

    /**
     * 查出所有分类与子分类，以树形结构组装起来
     *
     * @return
     */
    @Override
    public List<CategoryEntity> listWithTree() {
        // 查出所有分类
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
        // 组成父子的属树形结构

        // 找到所有的一级分类
        List<CategoryEntity> level_1_Menus = categoryEntityList.stream().filter(
                categoryEntity -> categoryEntity.getParentCid() == 0
        ).map((menu) -> {
            // 收集该菜单的子级菜单 返回 配合递归使用
            menu.setChildren(getChildrenList(menu, categoryEntityList));
            return menu;
        }).sorted((menu1, menu2) -> {
            // 对菜单进行比较排序 将前后两个菜单作比较之后得到排序结果 使用每个中的sort方法进行序号相减得到排序结果
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return level_1_Menus;
    }

    /**
     * 递归查找所有菜单的子菜单
     * 根据 root 在 all 中去递归查找自子级单
     *
     * @param root 当前菜单 父级菜单
     * @param all  全部菜单
     * @return
     */
    private List<CategoryEntity> getChildrenList(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter((categoryEntity) -> {
            // 过滤条件：如果所有菜单中的父级id 等于 当前惨淡的id 那么就是它的子级菜单 就收集结果
            return categoryEntity.getParentCid().equals(root.getCatId());
        }).map((categoryEntity) -> {
            // 子菜单可能还会有子菜单 所以利用递归继续向下找子菜单 categoryEntity：当前菜单 all：所有菜单
            categoryEntity.setChildren(getChildrenList(categoryEntity, all));
            return categoryEntity;
        }).sorted((menu1, menu2) -> {
            // 还是需要进行排序操作
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return children;
    }


    /**
     * 批量删除
     *
     * @param asList
     */
    @Override
    public void removeMenuByIds(List<Long> asList) {
        // TODO: 2023/1/10 检查当前删除菜单是否被其他地方引用
        baseMapper.deleteBatchIds(asList);
    }

    /**
     * 查询catelogId的完整三级菜单路径
     *
     * @param catelogId
     * @return
     */
    @Override
    public Long[] findCategoryPath(Long catelogId) {
        List<Long> path = new ArrayList<>();
        // 通过定义递归方法 收集查找到的数据
        List<Long> parentPath = findParentPath(catelogId, path);
        // 将得到的逆序路径 转换为正序
        Collections.reverse(parentPath);

        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * 通过定义递归方法 收集查找到的数据
     *
     * @param catelogId
     * @return
     */
    private List<Long> findParentPath(Long catelogId, List<Long> path) {
        // 收集当前节点id
        path.add(catelogId);
        // 查询当前id的所属分类
        CategoryEntity byId = this.getById(catelogId);
        // 判断是否有父分类 有就继续向上查找
        if (byId.getParentCid() != 0) {
            // 递归 收集查找到的父id
            findParentPath(byId.getParentCid(), path);
        }
        return path;
    }

    /**
     * 级联修改
     *
     * @param category
     */
//    @Caching(evict = {// 需要对多个缓存进行多种缓存操作时 使用组合缓存操作
//            @CacheEvict(value = {"category"}, key = "'getLevel1Categorys'"),
//            @CacheEvict(value = {"category"}, key = "'getCatalogJson'")
//    })
    @CacheEvict(value = {"category"},allEntries = true)  // 删除该分区下的 所有缓存数据 @CacheEvict：失效模式   @CachePut：双写模式
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateCascade(CategoryEntity category) {
        this.updateById(category);
        categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
    }

    /**
     * 查询一级分类
     *
     * @return
     */
    @Cacheable(value = {"category"}, key = "#root.method.name",sync = true) // sync = true:开启本地锁
    @Override
    public List<CategoryEntity> getLevel1Categorys() {

        List<CategoryEntity> categoryEntityList = this.baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, 0));

        return categoryEntityList;
    }

    /**
     * 将分类列表以json格式返回（加入Cache缓存）
     *
     * @return
     */
    @Cacheable(value = {"category"}, key = "#root.methodName")
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
        // 接口优化 将多次查询 变为一次查询 多次程序处理
        List<CategoryEntity> selectList = this.baseMapper.selectList(null);
        // 查出所有一级分类
        List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
        // 封装数据
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            // 每一个一级分类查到一级分类的二级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            // 封装上边的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(level2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, level2.getCatId().toString(), level2.getName());
                    // 找当前二级分类的三级分类封装成vo
                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, level2.getCatId());
                    if (level3Catelog != null) {
                        List<Catelog2Vo.Category3Vo> category3Vos = level3Catelog.stream().map(level3 -> {
                            // 封装成指定格式
                            Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName());
                            return category3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(category3Vos);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        return parent_cid;
    }

    /**
     * 将分类列表以json格式返回 (缓存版)
     *
     * @return
     */
//    @Override
//    public Map<String, List<Catelog2Vo>> getCatalogJson2() {
//        /**
//         * 1、空结果缓存：解决缓存穿透问题
//         * 2、设置过期时间(加随机值)：解决缓存雪崩
//         * 3、加锁：解决缓存击穿问题
//         */
//
//        // 加入缓存逻辑
//        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
//        // 判断缓存中是否有该数据
//        if (StringUtils.isEmpty(catalogJson)) {
//            // 若果没有就是去数据库查
//            Map<String, List<Catelog2Vo>> catalogJsonFormDb = getCatalogJsonFormDbWithRedisLock();
//
//            return catalogJsonFormDb;
//        }
//        // 保存后将数据逆转会指定数据格式返回
//        Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
//        });
//        return result;
//    }

    /**
     * 将分类列表以json格式返回 (Redis缓存分布式版)
     *
     * @return
     */
//    public Map<String, List<Catelog2Vo>> getCatalogJsonFormDbWithRedisLock() {
//        // uuid值区分锁
//        String uuid = UUID.randomUUID().toString();
//        // 抢占分布式锁 (就是去redis占锁：set一个key为lock的，set进去了就是占锁成功)  并设置锁的过期时间
//        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 60, TimeUnit.SECONDS);
//        // 判断是否枷锁成功 true 成功 false失败
//        if (lock) {
//            System.out.println("获取分布式锁成功.....");
//            // 加锁成功 并设置锁的过期时间 并且锁时间设置与加锁是原子的 redisTemplate.expire("lock",30,TimeUnit.SECONDS)
//            Map<String, List<Catelog2Vo>> dataFormDb;
//            try {
//                // 执行业务
//                dataFormDb = getDataFormDb();
//            } finally {
//                // 获取值对比 + 对比成功后删除锁 = 必须是一个原子的操作 -> 可以使用Lua脚本来完成
//                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//                // 解锁-删除锁
//                redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
//            }
////            // 业务执行完成 解锁前判断当前锁还是不是自己的锁 是的话解锁 不是则不能删除 查询值与删除操作也必须是一个原子操作
////            String lockValue = redisTemplate.opsForValue().get("lock");
////            if (uuid.equals(lockValue)){
////                // 解锁-删除锁
////                redisTemplate.delete("lock");
////            }
//            return dataFormDb;
//        } else {
//            System.out.println("获取分布式锁失败.....等待重试");
//            // 让线程休眠一会 防止调用过快 造成内存溢出
//            try {
//                Thread.sleep(200);
//            } catch (Exception e) {
//
//            }
//            // 加锁失败 重试 自旋式等待
//            return getCatalogJsonFormDbWithRedisLock();
//        }
//    }

    /**
     * 将分类列表以json格式返回 (Redisson缓存分布式版)
     * 加入缓存与数据的一致性问题
     * 常用的两种方式：双写模式（同时向缓存数据库写入数据）
     * 失效模式（数据发生变化后，直接删除对应缓存，等待下次主动查询重新查询存入缓存）
     *
     * @return
     */
//    public Map<String, List<Catelog2Vo>> getCatalogJsonFormDbWithRedissonLock() {
//
//        // 注意锁的名字 锁的粒度，越细越快
//        RLock lock = redisson.getLock("catalogJson-lock");
//        // 加锁
//        lock.lock();
//
//        Map<String, List<Catelog2Vo>> dataFormDb;
//        try {
//            // 执行业务
//            dataFormDb = getDataFormDb();
//        } finally {
//            // 解锁
//            lock.unlock();
//        }
//        return dataFormDb;
//    }

    /**
     * 获取分类列表
     *
     * @return
     */
    private Map<String, List<Catelog2Vo>> getDataFormDb() {

        // 得到锁之后 去缓存查有没有数据 没有继续查库，有直接返回
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        // 判断缓存中是否有该数据
        if (!StringUtils.isEmpty(catalogJson)) {
            // 有 直接将数据逆转会指定数据格式返回
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return result;
        }

        // 接口优化 将多次查询 变为一次查询 多次程序处理
        List<CategoryEntity> selectList = this.baseMapper.selectList(null);

        // 查出所有一级分类
        List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
        // 封装数据
        Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            // 每一个一级分类查到一级分类的二级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            // 封装上边的结果
            List<Catelog2Vo> catelog2Vos = null;
            if (categoryEntities != null) {
                catelog2Vos = categoryEntities.stream().map(level2 -> {
                    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, level2.getCatId().toString(), level2.getName());
                    // 找当前二级分类的三级分类封装成vo
                    List<CategoryEntity> level3Catelog = getParent_cid(selectList, level2.getCatId());
                    if (level3Catelog != null) {
                        List<Catelog2Vo.Category3Vo> category3Vos = level3Catelog.stream().map(level3 -> {
                            // 封装成指定格式
                            Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName());
                            return category3Vo;
                        }).collect(Collectors.toList());
                        catelog2Vo.setCatalog3List(category3Vos);
                    }
                    return catelog2Vo;
                }).collect(Collectors.toList());
            }
            return catelog2Vos;
        }));
        // 将查到的数据存到缓存中一份,要将查需要拿到的数据转换为json在保存进去 并设置过期时间 - 本地所部分
        String catalogJsonFormDb_JsonString = JSON.toJSONString(parent_cid);
        redisTemplate.opsForValue().set("catalogJson", catalogJsonFormDb_JsonString, 1, TimeUnit.SECONDS);
        return parent_cid;
    }

    /**
     * 将分类列表以json格式返回 (db缓存单体版)
     *
     * @return
     */
//    public Map<String, List<Catelog2Vo>> getCatalogJsonFormDbWithLocalLock() {
//
//        // 只要是同一把锁，就能锁住需要这个锁的所有线程 单体加锁方式 只能锁住当前进程资源
//        synchronized (this) {
//            // 得到锁之后 去缓存查有没有数据 没有继续查库，有直接返回
//            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
//            // 判断缓存中是否有该数据
//            if (!StringUtils.isEmpty(catalogJson)) {
//                // 有 直接将数据逆转会指定数据格式返回
//                Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
//                });
//                return result;
//            }
//
//            // 接口优化 将多次查询 变为一次查询 多次程序处理
//            List<CategoryEntity> selectList = this.baseMapper.selectList(null);
//
//            // 查出所有一级分类
//            List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
//            // 封装数据
//            Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
//                // 每一个一级分类查到一级分类的二级分类
//                List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
//                // 封装上边的结果
//                List<Catelog2Vo> catelog2Vos = null;
//                if (categoryEntities != null) {
//                    catelog2Vos = categoryEntities.stream().map(level2 -> {
//                        Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, level2.getCatId().toString(), level2.getName());
//                        // 找当前二级分类的三级分类封装成vo
//                        List<CategoryEntity> level3Catelog = getParent_cid(selectList, level2.getCatId());
//                        if (level3Catelog != null) {
//                            List<Catelog2Vo.Category3Vo> category3Vos = level3Catelog.stream().map(level3 -> {
//                                // 封装成指定格式
//                                Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo(level2.getCatId().toString(), level3.getCatId().toString(), level3.getName());
//                                return category3Vo;
//                            }).collect(Collectors.toList());
//                            catelog2Vo.setCatalog3List(category3Vos);
//                        }
//                        return catelog2Vo;
//                    }).collect(Collectors.toList());
//                }
//                return catelog2Vos;
//            }));
//            // 将查到的数据存到缓存中一份,要将查需要拿到的数据转换为json在保存进去 并设置过期时间 - 本地所部分
//            String catalogJsonFormDb_JsonString = JSON.toJSONString(parent_cid);
//            redisTemplate.opsForValue().set("catalogJson", catalogJsonFormDb_JsonString, 1, TimeUnit.SECONDS);
//            return parent_cid;
//        }
//    }


    /**
     * 抽取方法 根据parent_cid查询CategoryEntity数据
     *
     * @param selectList
     * @param parent_cid
     * @return
     */
    private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {
        List<CategoryEntity> collect = selectList.stream().filter(item -> item.getParentCid().equals(parent_cid)).collect(Collectors.toList());
        return collect;
    }
}
